package com.wus.gateway.config;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.wus.common.core.account.AccountThreadLocal;
import com.wus.common.core.constant.AccountConstants;
import com.wus.common.core.domain.base.Result;
import com.wus.common.core.domain.user.AccountInfo;
import com.wus.gateway.config.properties.SaTokenProperties;
import com.wus.gateway.exception.NotAuthException;
import com.wus.gateway.util.JwtUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Objects;

/**
 * @Description: [Sa-Token 权限认证] 全局配置类
 * @Author: wus
 * @Date: 2024/12/17 15:36
 */
@Configuration
public class SaTokenConfigure {

    private final Logger logger = LoggerFactory.getLogger(SaTokenConfigure.class);

    final SaTokenProperties saTokenProperties;
    final JwtUtils jwtUtils;

    public SaTokenConfigure(SaTokenProperties saTokenProperties,
                            JwtUtils jwtUtils) {
        this.saTokenProperties = saTokenProperties;
        this.jwtUtils = jwtUtils;
    }

    /**
     * 注册 [Sa-Token全局过滤器]
     */
    @Bean
    public SaReactorFilter getSaReactorFilter() {
        return new SaReactorFilter()
                // 指定 [拦截路由]
                .addInclude("/**")    /* 拦截所有path */
                // 指定 [放行路由]
                .addExclude(saTokenProperties.getWhitelist() == null ? new String[]{} : saTokenProperties.getWhitelist().toArray(new String[]{}))
                // 指定[认证函数]: 每次请求执行
                .setAuth(obj -> {
                    logger.info("---------- sa全局认证检测开始");
                    if (!StpUtil.isLogin()) {
                        logger.warn("---------- 未认证！");
                        throw new NotAuthException("请先登录认证！");
                    }
                    // 查询用户上下文信息往下传
                    String jwtToken = SaHolder.getRequest().getHeader(AccountConstants.ACCOUNT_JWT_TOKEN_HEADER);
                    if (StrUtil.isBlank(jwtToken)) {
                        throw new NotAuthException("还缺失jwtToken请求头！");
                    }
                    AccountInfo accountInfo = jwtUtils.parseToken(jwtToken, AccountInfo.class);
                    if (StpUtil.getLoginId(-1L).compareTo(accountInfo.getUserId()) == 0 && Objects.equals(StpUtil.getTokenValue(), accountInfo.getLoginToken())) {
                        // 主要确保loginToken跟对应的jwtToken一一对应，避免盗用、伪造jwtToken
                        logger.info("---------- 登录信息匹配，放行！");
                    } else {
                        logger.warn("---------- 登录信息不匹配，拦截！");
                        throw new NotAuthException("登录信息不匹配，请重新登录！");
                    }
                    AccountThreadLocal.set(accountInfo);
                    logger.info("---------- sa全局认证通过, loginId={}", StpUtil.getLoginId());
                })
                // 指定[异常处理函数]：每次[认证函数]发生异常时执行此函数
                .setError(e -> {
                    logger.error("sa全局异常：", e);
                    // 释放资源
                    AccountThreadLocal.remove();

                    SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");
                    if (e instanceof NotAuthException || e instanceof NotLoginException || e instanceof SaTokenException) {
                        return JSONUtil.toJsonStr(Result.fail(AccountConstants.ACCOUNT_NOT_LOGIN_CODE, e.getMessage()));
                    } else {
                        return JSONUtil.toJsonStr(Result.fail(e.getMessage()));
                    }
                })
                // 前置函数：在每次认证函数之前执行（BeforeAuth 不受 includeList 与 excludeList 的限制，所有请求都会进入）
                .setBeforeAuth(r -> {
                    // ---------- 设置一些安全响应头 ----------
                    SaHolder.getResponse()
                            // 服务器名称
                            .setServer("sa-server")
                            // 是否可以在iframe显示视图： DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
                            .setHeader("X-Frame-Options", "SAMEORIGIN")
                            // 是否启用浏览器默认XSS防护： 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时，停止渲染页面
                            .setHeader("X-XSS-Protection", "1; mode=block")
                            // 禁用浏览器内容嗅探
                            .setHeader("X-Content-Type-Options", "nosniff")
                    ;
                });
    }
}
